package cn.xuewenbao.ucenter.websocket;

/**
 * @Auther: mafayu
 */

import cn.xuewenbao.ucenter.entity.Member;
import cn.xuewenbao.ucenter.entity.Message;
import cn.xuewenbao.ucenter.entity.vo.MessageVo;

import cn.xuewenbao.ucenter.service.MemberService;
import cn.xuewenbao.ucenter.service.MessageService;
import cn.xuewenbao.ucenter.service.impl.MemberServiceImpl;
import cn.xuewenbao.ucenter.service.impl.MessageServiceImpl;
import cn.xuewenbao.ucenter.utils.SpringUtil;
import com.alibaba.fastjson.JSON;

import lombok.extern.slf4j.Slf4j;

import lombok.val;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;


import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 前后端交互的类实现消息的接收推送
 *
 * @ServerEndpoint(value = "/test/oneToOne") 前端通过此URI和后端交互，建立连接
 */
@Component
@DependsOn("springUtil")
@Slf4j
@ServerEndpoint(value = "/ucenter/test/oneToOne/{userId}")
public class OneWebSocket {
    /**
     * 记录当前在线连接数
     */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /**
     * 存放所有在线的session
     */
    // <userId, Set<sessionId>>, 一个用户的所有session的id集合
    private static Map<String, ConcurrentSkipListSet<String>> userSessionIds = new ConcurrentHashMap<>();
    // <sessionId, session>
    private static Map<String, Session> clients = new ConcurrentHashMap<>();


    private MemberService memberService = SpringUtil.getBean(MemberServiceImpl.class);

    private MessageService messageService = SpringUtil.getBean(MessageServiceImpl.class);

    private String userId;

    // <userId, List<message>>  发送给一个用户的离线消息
    private static Map<String, List<MessageVo>> offlineMessage = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(@PathParam("userId") String userId,
                       Session session) {
        this.userId = userId;
        Member member = memberService.getById(userId);
        ConcurrentSkipListSet<String> sessionIdSet = userSessionIds.get(userId);    //获取该用户的在线sessionId集合
        //其他设备已登录, 添加当前的session为新设备
        if (sessionIdSet != null && !sessionIdSet.isEmpty()) {   //其他设备已经登录
            sessionIdSet.add(session.getId());         //将当前 sessionId加入该用户的在线 sessionId集合
            clients.put(session.getId(), session);     //将当前 sessionId和 session保存
            log.info("{}的新设备已上线，当前在线人数为：{}", member.getUsername(), onlineCount.get());
            return;
        }
        //第一个登录的设备
        sessionIdSet = new ConcurrentSkipListSet<>();
        sessionIdSet.add(session.getId());
        userSessionIds.put(userId, sessionIdSet);
        clients.put(session.getId(), session);
        onlineCount.incrementAndGet(); // 在线数加1
        log.info("{}已上线，当前在线人数为：{}", member.getUsername(), onlineCount.get());
        //推送该用户的离线消息
        if (offlineMessage.get(userId) != null) {
            List<MessageVo> list = offlineMessage.get(userId);
            for (MessageVo vo : list) {
                String message = JSON.toJSONString(vo);
                Member sender = memberService.getById(vo.getSendId());
                Member reveiver = memberService.getById(vo.getReceiveId());
                String senderName = sender != null ? sender.getUsername() : "";
                String reveiverName = reveiver != null ? reveiver.getUsername() : "";
                sendTextMessage(message, senderName, reveiverName, session);
            }
            offlineMessage.remove(userId);
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        Set<String> sessionIdSet = userSessionIds.get(userId);
        sessionIdSet.remove(session.getId());   //从该用户的在线 sessionId集合中删除
        clients.remove(session.getId());        //将 session通过 sessionId删除
        if (sessionIdSet.isEmpty()) {
            onlineCount.decrementAndGet(); // 在线数减1
            log.info("该用户所有连接已关闭：{}，当前在线人数为：{}", this.userId, onlineCount.get());
        } else {
            log.info("一个连接已经关闭：{}，当前在线人数为：{}", this.userId, onlineCount.get());
        }
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        try {
            MessageVo myMessage = JSON.parseObject(message, MessageVo.class);
            if (myMessage != null) {
                String sendId = myMessage.getSendId();
                String receiveId = myMessage.getReceiveId();
                Integer type = myMessage.getType();
                Member memberSend = memberService.getById(sendId);
                String context = JSON.toJSONString(myMessage);
                Member memberRece = memberService.getById(receiveId);
                //存入数据库
                Message mes = new Message();
                mes.setSendId(sendId);
                mes.setReceiveId(receiveId);
                mes.setContext(context);
                mes.setType(type);
                final val save = messageService.save(mes);
                if (save == false) {
                    log.error("存入数据库失败");
                }
                SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                myMessage.setDatetime(format.format(mes.getGmtCreate()));
                context = JSON.toJSONString(myMessage);
                log.info("用户[{}]给用户[{}]发送消息[{}]", memberSend.getUsername(), memberRece.getUsername(), message);
                Set<String> userSessionSet = userSessionIds.get(receiveId);
                if (userSessionSet != null) {   //接收用户有在线的 sessionId
                    for (String sessionId : userSessionSet) {
                        Session toSession = clients.get(sessionId);
                        if (toSession != null) {
                            this.sendTextMessage(context, memberSend.getUsername(), memberRece.getUsername(), toSession);
                        }
                    }
                } else {    //接收用户离线, 发送离线消息
                    this.sendOfflineMessage(context, memberSend.getUsername(), memberRece.getUsername());
                }
                //给发送方的其他设备也发送
                Set<String> sendSessionSet = userSessionIds.get(sendId);
                if (sendSessionSet != null) {
                    for (String sessionId : sendSessionSet) {
                        if (session.getId().equals(sessionId)) {    //本设备跳过
                            continue;
                        }
                        Session toSession = clients.get(sessionId);
                        if (toSession != null) {
                            this.sendTextMessage(context, memberSend.getUsername(), memberRece.getUsername(), toSession, true);
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("解析失败：{}", e);
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    /**
     * 服务端发送文本消息给客户端
     */
    private void sendTextMessage(String message, String sender, String receiver, Session toSession) {
        sendTextMessage(message, sender, receiver, toSession, false);
    }

    /**
     * 服务端发送文本消息给客户端, isSync: 是否是客户端发送给同一用户的其他客户端的同步消息
     */
    private void sendTextMessage(String message, String sender, String receiver, Session toSession, Boolean isSync) {
        //发送在线消息
        if (toSession.isOpen()) {
            try {
                if (!isSync) {  //一个用户发给另一个用户的
                    log.info("用户[{}]的设备[{}]收到用户[{}]的消息[{}]", receiver, toSession.getId(), sender, message);
                } else {
                    log.info("用户[{}]的设备[{}]收到其他设备的同步消息[{}]", sender, toSession.getId(), message);
                }
                toSession.getBasicRemote().sendText(message);
            } catch (Exception e) {
                log.error("服务端发送消息给客户端失败：{}", e);
            }
        }
    }

    private void sendOfflineMessage(String message, String sender, String receiver) {
        MessageVo vo = JSON.parseObject(message, MessageVo.class);
        String receiveId = vo.getReceiveId();
        List<MessageVo> list = offlineMessage.get(receiveId);    //获取接收方的离线消息列表
        if (list == null) {
            list = new ArrayList<>();
        }
        list.add(vo);
        offlineMessage.put(receiveId, list);
        log.info("用户[{}]未上线，发送离线消息", receiver);
    }
}
